﻿using System;
using System.Xml;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;

using CubeBuilder.Terrain;

namespace CubeBuilder
{
    //Map generation uses Perlin Noise and sorts the cubes into chunks which are then rendered using a
    //static vertex buffer.
    //They chunks are also sorted into Bounding Boxes for culling
    //TODO: Texture Atlasing

    class BlockXML
    {
        //Class for Intermediate Serializer to store map
        public List<Vector3>[][] Grass, Water, Dirt, Stone, Sand;
    }

    public class Game1 : Microsoft.Xna.Framework.Game
    {
        //Effects/Frustum
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        private StaticVBRenderer currentVBRenderer;
        private Effect staticVBEffect;
        BoundingFrustum bf;
        SpriteFont dFont;
        //Cube selections
        Vector4 intersecting = new Vector4(999, 999, 999, 999);
        int lastintersect = 999;
        //Camera Angles & Position / FrameRate
        bool jumping = false, flying = false, ignorgravity = false; //jumping variables
        int jumpstart;
        float aspectRatio = 0.0f;
        float leftrightRot = MathHelper.PiOver2; //handle camera rotated orientation
        float updownRot = -MathHelper.Pi / 10.0f; //for flying orientation
        const float rotationSpeed = 0.3f, moveSpeed = 30.0f; //Moving/Camera rotation
        MouseState originalMouseState;
        Vector2 lastPos; //for finding top
        int frameRate, frameCounter = 0; //frame rate
        TimeSpan elapsedTime = TimeSpan.Zero;
        int seedx; //map seed
        //Map Dimentions
        const int cubenumbery = 5; //depth from sea (y)
        static int width = 16, length = 16; //size of cubes
        static int chunks = 400; //must be root number
        static double xychunks = Math.Sqrt((double)chunks);
        //Cube list / render array
        private StaticVBRenderer[,][] staticVBRenderer = new StaticVBRenderer[(int)xychunks, (int)xychunks][];
        private List<Vector3>[,] Grass, Water, Sand, Dirt, Stone; //Set block lists
        // Content     
        BlockXML location = new BlockXML(); //Map class for serialization
        XmlWriterSettings settings = new XmlWriterSettings(); //XML Writer settings
        BasicEffect cubeEffect; //init effect
        Texture2D[] Textures = new Texture2D[6];
        BoundingBox[,] thisqube = new BoundingBox[(int)xychunks, (int)xychunks];
        // Position related variables
        Vector3 cameraPosition = new Vector3(0, 80, 0);
        Vector3 modelPosition = Vector3.Zero;
        // Misc Declarations
        float timedonesince;
        bool NtUpdate = false; //Does game need to update?
        int drawn = 0; //Show chunks being drawn
        Random r = new Random(); //for random numbers

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content"; 
            //graphics.SynchronizeWithVerticalRetrace = false;
            IsFixedTimeStep = false;
            graphics.PreferredBackBufferHeight = 720;
            graphics.PreferredBackBufferWidth = 1280; 
            TargetElapsedTime = TimeSpan.FromTicks(333333);
        }

        protected override void Initialize()
        {
            //Textures
            Textures[2] = Content.Load<Texture2D>("Textures/dirt1");
            dFont = Content.Load<SpriteFont>("default"); //load font for stats
            this.staticVBEffect = Content.Load<Effect>("StaticVertexBuffer");
            Textures[1] = Content.Load<Texture2D>("Textures/grasstop");
            Textures[3] = Content.Load<Texture2D>("Textures/water");
            Textures[0] = Content.Load<Texture2D>("Textures/Stone");
            Textures[4] = Content.Load<Texture2D>("Textures/Sand");
            //Call map generation, at bottom
            //LoadMap();
            GenerateMap();

            base.Initialize();
        }

        protected override void LoadContent()
        {

            cubeEffect = new BasicEffect(GraphicsDevice); //initialize a new effect
            UpdateViewMatrix(); //update view
            cubeEffect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 0.3f, 1000.0f);

            Mouse.SetPosition(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
            originalMouseState = Mouse.GetState();

            spriteBatch = new SpriteBatch(GraphicsDevice);// Create a new SpriteBatch, which can be used to draw textures.

            aspectRatio = GraphicsDevice.Viewport.AspectRatio;//update aspect ratio

            cubeEffect.TextureEnabled = true; //Enable textures

            // Set the World matrix which defines the position of the cube
            cubeEffect.LightingEnabled = true;
            cubeEffect.DirectionalLight0.Enabled = true;
            cubeEffect.AmbientLightColor = new Vector3(0.2f, 0.2f, 0.2f);
            cubeEffect.EmissiveColor = new Vector3(1, 0, 0);
        }

        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        protected override void Update(GameTime gameTime)
        {
            float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;
            if (this.IsActive == true) //only process input if the window is active
                ProcessInput(timeDifference); //movement

            //Mouse values/Center
            IsMouseVisible = false;

            bf = new BoundingFrustum(cubeEffect.View * cubeEffect.Projection); //for bounding frustum
            elapsedTime += gameTime.ElapsedGameTime;
            timedonesince = (float)gameTime.ElapsedGameTime.TotalSeconds;

            if (elapsedTime > TimeSpan.FromSeconds(1)) //Measure frame rate
            {
                elapsedTime -= TimeSpan.FromSeconds(1);
                frameRate = frameCounter;
                frameCounter = 0;
            }

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);
            // Set the Projection matrix which defines how we see the scene (Field of view)
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            cubeEffect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio, 1.0f, 1000.0f);
            frameCounter++;
            DrawCubes(); //Draw the chunks
        }

        private void DrawCubes()
        {
            foreach (EffectPass pass in cubeEffect.CurrentTechnique.Passes)
            {
                for (byte chunkx = 0; chunkx < mapsize.X; chunkx++)
                {
                    for (byte chunkz = 0; chunkz < mapsize.Y; chunkz++)
                    {
                        if (NtUpdate == true) //If game needs to save chunks eg. Block moved/broken
                            SaveMap(); //Save map

                        //if (InView(thisqube[chunkx, chunkz]) == false)
                        //{
                        //    UnLoadBuffer(chunkx, chunkz);
                        //    continue; //Checks if chunk is visible first.. Very rough
                        //}
                        //else
                        //    LoadBuffer(chunkx, chunkz);//load the chunk back into the buffer

                        //Checks to see which terrain type to render, until I do texture atlasing
                        for (int terraintype = 0; terraintype < 5; terraintype++)
                        {
                            if (terraintype == 0)
                                currentVBRenderer = this.staticVBRenderer[chunkx, chunkz][0];
                            if (terraintype == 1)
                                currentVBRenderer = this.staticVBRenderer[chunkx, chunkz][1];
                            if (terraintype == 2)
                                currentVBRenderer = this.staticVBRenderer[chunkx, chunkz][2];
                            if (terraintype == 3)
                                currentVBRenderer = this.staticVBRenderer[chunkx, chunkz][3];
                            if (terraintype == 4)
                                currentVBRenderer = this.staticVBRenderer[chunkx, chunkz][4];
                            if (currentVBRenderer == null)
                                continue;
                            //Render that buffer
                            currentVBRenderer.Render(cubeEffect.View, cubeEffect.Projection, Textures[terraintype]);
                            drawn++; //update draw call stats
                        }
                    }
                }
            }
            //Draw fps and whatnot
            string guess = "Total Draws: " + drawn;
            string fps = string.Format("FPS: {0}", frameRate);
            string flyingstatus = "Flying: " + flying.ToString();
            string Yposstat = "Ypos: " + cameraPosition.Y.ToString() + " | Gravity: " + ignorgravity.ToString();
            string crosshair = "+";
            string intersectingbox = "Intersect: " + intersecting.ToString();
            spriteBatch.Begin(); //being spritebatch to draw stats
            spriteBatch.DrawString(dFont, guess, new Vector2(10, 10), Color.Red);
            spriteBatch.DrawString(dFont, fps, new Vector2(10, 30), Color.Red);
            spriteBatch.DrawString(dFont, flyingstatus, new Vector2(10, 50), Color.Red);
            spriteBatch.DrawString(dFont, Yposstat, new Vector2(10, 70), Color.Red);
            if(intersecting.X != 999)
                spriteBatch.DrawString(dFont, intersectingbox, new Vector2(10, 90), Color.Red);
            spriteBatch.DrawString(dFont, crosshair, new Vector2(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2), Color.Red);
            spriteBatch.End();
            drawn = 0; //reset draw calls
        }

        private void UnLoadBuffer(int x, int z)
        {
            //unload buffer for the chunk
            this.staticVBRenderer[x, z][0] = null;
            this.staticVBRenderer[x, z][1] = null;
            this.staticVBRenderer[x, z][2] = null;
            this.staticVBRenderer[x, z][3] = null;
            this.staticVBRenderer[x, z][4] = null;
        }

        private void LoadBuffer(int chunkx, int chunkz)
        {
            //reload the chunk that was unloaded! (slow =[)
            if(this.staticVBRenderer[chunkx, chunkz][0] == null)
                if (Stone[chunkx, chunkz].Count != 0)
                    this.staticVBRenderer[chunkx, chunkz][0] = new StaticVBRenderer(GraphicsDevice, Textures[0], Stone[chunkx, chunkz], staticVBEffect);
            if (this.staticVBRenderer[chunkx, chunkz][1] == null)
                if (Grass[chunkx, chunkz].Count != 0)
                    this.staticVBRenderer[chunkx, chunkz][1] = new StaticVBRenderer(GraphicsDevice, Textures[1], Grass[chunkx, chunkz], staticVBEffect);
            if (this.staticVBRenderer[chunkx, chunkz][2] == null)
                if (Dirt[chunkx, chunkz].Count != 0)
                    this.staticVBRenderer[chunkx, chunkz][2] = new StaticVBRenderer(GraphicsDevice, Textures[2], Dirt[chunkx, chunkz], staticVBEffect);
            if (this.staticVBRenderer[chunkx, chunkz][3] == null)
                if (Water[chunkx, chunkz].Count != 0)
                    this.staticVBRenderer[chunkx, chunkz][3] = new StaticVBRenderer(GraphicsDevice, Textures[3], Water[chunkx, chunkz], staticVBEffect);
            if(this.staticVBRenderer[chunkx, chunkz][4] == null)
                if (Sand[chunkx, chunkz].Count != 0)
                    this.staticVBRenderer[chunkx, chunkz][4] = new StaticVBRenderer(GraphicsDevice, Textures[4], Sand[chunkx, chunkz], staticVBEffect);
        }

        private void LoadMap()
        {
            //Prepare / Initialize lists
            PrepareLists();

            //Locate app path for map folder
            string mapPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
            XmlReaderSettings settings = new XmlReaderSettings();

            //Start loading XML by chunk (Check map folder)
            using (XmlReader reader = XmlReader.Create(mapPath + "\\Map\\Map.xml", settings))
            {
                location = IntermediateSerializer.Deserialize<BlockXML>(reader, null);
                for (byte chunkx = 0; chunkx < xychunks; chunkx++)
                {
                    for (byte chunkz = 0; chunkz < xychunks; chunkz++)
                    {
                        //Load the XML
                        //assign the Vector 3 lists for each chunk.
                        Grass[chunkx, chunkz] = location.Grass[chunkx][chunkz];
                        Water[chunkx, chunkz] = location.Water[chunkx][chunkz];
                        Stone[chunkx, chunkz] = location.Stone[chunkx][chunkz];
                        Dirt[chunkx, chunkz] = location.Dirt[chunkx][chunkz];
                        Sand[chunkx, chunkz] = location.Sand[chunkx][chunkz];
                    }
                }
            }
            LoadBuffers();//load the buffers

            BoundingCreation(); //Create Bounding boxes
            //clear xml class
            location.Grass = null;
            location.Water = null;
            location.Sand = null;
            location.Dirt = null;
            location.Stone = null;

            NtUpdate = false; //Tell game update save not needed.
        }
        float biome;
        private void GenerateMap()
        {
            seedx = r.Next(60000); //Generate Map Seed

            PrepareLists(); //Prepare / Initialize lists

            //Generate map in chunks
            for (byte chunkx = 0; chunkx < xychunks; chunkx++)
            {
                for (byte chunkz = 0; chunkz < xychunks; chunkz++)
                {
                    biome = r.Next(0, 5);
                    for (int x = 0 + (16 * (int)chunkx); x < width + (16 * (int)chunkx); x++)
                    {
                        for (int z = 0 + (16 * (int)chunkz); z < length + (16 * (int)chunkz); z++)
                        {
                            //15,10 | 30, 8 | 20, 5 <----- Octave 4/5 combos that are good so far
                            float octave1 = PerlinSimplexNoise.noise((x + seedx) * 0.0001f, 1, (z + seedx) * 0.0001f) * 0;
                            float octave2 = PerlinSimplexNoise.noise((x + seedx) * 0.0005f, 1, (z + seedx) * 0.0005f) * 0f;
                            float octave3 = PerlinSimplexNoise.noise((x + seedx) * 0.005f, 1, (z + seedx) * 0.005f) * 0;
                            float octave4 = PerlinSimplexNoise.noise((x + seedx) * 0.01f, 1, (z + seedx) * 0.01f) * 20f;
                            float octave5 = PerlinSimplexNoise.noise((x + seedx) * 0.03f, 1, (z + seedx) * 0.03f) * 5f;

                            float finalnumber = octave2 + octave3 + octave4 + octave5;

                            float lowerGroundHeight = octave1 + octave2 + octave3 + octave4 + octave5;
                            if ((67 + (int)lowerGroundHeight) <= 64) //Check if water
                            {
                                Sand[chunkx, chunkz].Add(new Vector3(x, 68 + (int)lowerGroundHeight, z)); //Add to water list

                                //if (67 + (int)lowerGroundHeight < 64)
                                //{
                                //    for (int tosea = (67 + (int)lowerGroundHeight); tosea < 65; tosea++) //Create lakes/oceans depth
                                //    {
                                Water[chunkx, chunkz].Add(new Vector3(x, 65, z)); //Also add to list
                                //    }
                                //}
                            }
                            else
                                Grass[chunkx, chunkz].Add(new Vector3(x, 67 + (int)lowerGroundHeight, z)); //Add grass to list


                            //This will fill in all cubes under ground level, disabled as of now because it overflows the buffers..
                            //Although I suppose I'd want it to be stored in a list but just not redered or buffered until visible

                            //for (int tobottom = (54 + (int)lowerGroundHeight); tobottom > (54 + (int)lowerGroundHeight) - 3; tobottom--)
                            //{
                            //    if (r.Next(100) > 50)
                            //    {
                            //        Dirt[chunkx, chunkz].Add(new Vector3(x, tobottom, z)); //Add to list
                            //    }
                            //    else
                            //    {
                            //        Stone[chunkx, chunkz].Add(new Vector3(x, tobottom, z)); //Add to list
                            //    }
                            //}
                            //}
                        }
                    }
                }
            }
            mapsize = new Vector2((float)xychunks, (float)xychunks);
            LoadBuffers(); //Load buffers

            BoundingCreation(); //Create Bounding Boxes.
        }

        private void SaveMap()
        {
            //initiate jagged arrays part one
            location.Grass = new List<Vector3>[(int)xychunks][];
            location.Water = new List<Vector3>[(int)xychunks][];
            location.Stone = new List<Vector3>[(int)xychunks][];
            location.Dirt = new List<Vector3>[(int)xychunks][];
            location.Sand = new List<Vector3>[(int)xychunks][];
            //Initiate second part of jagged arrays
            for (int i = 0; i < xychunks; i++)
            {
                location.Grass[i] = new List<Vector3>[(int)xychunks];
                location.Water[i] = new List<Vector3>[(int)xychunks];
                location.Stone[i] = new List<Vector3>[(int)xychunks];
                location.Dirt[i] = new List<Vector3>[(int)xychunks];
                location.Sand[i] = new List<Vector3>[(int)xychunks];
            }

            for (byte chunkx = 0; chunkx < xychunks; chunkx++)
            {
                for (byte chunkz = 0; chunkz < xychunks; chunkz++)
                {
                    //Load the Intermediate Seriealizer
                    location.Grass[chunkx][chunkz] = Grass[chunkx, chunkz];
                    location.Water[chunkx][chunkz] = Water[chunkx, chunkz];
                    location.Stone[chunkx][chunkz] = Stone[chunkx, chunkz];
                    location.Dirt[chunkx][chunkz] = Dirt[chunkx, chunkz];
                    location.Sand[chunkx][chunkz] = Sand[chunkx, chunkz];
                }
            }

            write("Map"); //Set name of XML to chunk name

            location.Grass = null;
            location.Water = null;
            location.Sand = null;
            location.Dirt = null;
            location.Stone = null;
            NtUpdate = false;
        }

        private void write(string name)
        {
            //write the xml to disk
            string mapPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
            settings.Indent = true;
            using (XmlWriter writer = XmlWriter.Create(mapPath + "\\Map\\Map.xml", settings))
            {
                IntermediateSerializer.Serialize(writer, location, null);
            }
        }

        private void PrepareLists()
        {

            //Prepare lists for data loading
            Grass = new List<Vector3>[(int)xychunks, (int)xychunks];
            Dirt = new List<Vector3>[(int)xychunks, (int)xychunks];
            Water = new List<Vector3>[(int)xychunks, (int)xychunks];
            Stone = new List<Vector3>[(int)xychunks, (int)xychunks];
            Sand = new List<Vector3>[(int)xychunks, (int)xychunks];

            //Initialize chunks/Renderer
            for (byte initx = 0; initx < xychunks; initx++)
            {
                for (byte inity = 0; inity < xychunks; inity++)
                {
                    Grass[initx, inity] = new List<Vector3>();
                    Dirt[initx, inity] = new List<Vector3>();
                    Water[initx, inity] = new List<Vector3>();
                    Stone[initx, inity] = new List<Vector3>();
                    Sand[initx, inity] = new List<Vector3>();
                    staticVBRenderer[initx, inity] = new StaticVBRenderer[5];
                }
            }
        }

        bool boxmade = false;
        List<BoundingBox> thetestbox = new List<BoundingBox>();
        private void ProcessInput(float amount)
        {
            MouseState currentMouseState = Mouse.GetState(); //get current mouse state
            if (currentMouseState != originalMouseState)
            {
                //if mouse moves, update the new screen settings
                float xDifference = currentMouseState.X - originalMouseState.X;
                float yDifference = currentMouseState.Y - originalMouseState.Y;
                leftrightRot -= rotationSpeed * xDifference * amount;
                updownRot -= rotationSpeed * yDifference * amount;
                //check if window is active
                Mouse.SetPosition(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
                UpdateViewMatrix();
            }
            //keyboard movements
            Vector3 moveVector = new Vector3(0, 0, 0);
            KeyboardState keyState = Keyboard.GetState();
            if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W))
                moveVector += new Vector3(0, 0, -1);
            if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S))
                moveVector += new Vector3(0, 0, 1);
            if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D))
                moveVector += new Vector3(1, 0, 0);
            if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A))
                moveVector += new Vector3(-1, 0, 0);
            if (keyState.IsKeyDown(Keys.Space)) //jump
                Jump(false);
            if (ignorgravity == true) //handle jumping state
                Jump(true);
            if (keyState.IsKeyDown(Keys.Escape)) //quit
                this.Exit();
            if (keyState.IsKeyDown(Keys.R)) //save map
                NtUpdate = true;
            if (keyState.IsKeyDown(Keys.F)) //flying
                flying = !flying;
            if (keyState.IsKeyDown(Keys.P)) //create cube
                CubeCreation();
            if (flying == true) //check if flying is enabled.
            {
                if (keyState.IsKeyDown(Keys.Subtract)) //up
                    moveVector += new Vector3(0, 1, 0);
                if (keyState.IsKeyDown(Keys.Add)) //down
                    moveVector += new Vector3(0, -1, 0);
            }
            else
                UpdateHeight(); //handle keeping playing on the ground
            AddToCameraPosition(moveVector * amount); //update position
            if (ignorgravity == false && (int)cameraPosition.Y == findtop() + 2)
                jumping = false;
            MapExpand();
        }

        bool stopcreation = false;
        private void MapExpand()
        {
            if (stopcreation == false)
            {
                if (lastPos.Y + 2 > mapsize.Y)
                {
                    stopcreation = true;
                    Vector2 expansion = new Vector2(lastPos.X, lastPos.Y + 2);
                    Expand(expansion);
                }
            }
        }

        private void ResizeArray(ref List<Vector3>[,] original, int cols, int rows)
        {
            //create a new 2 dimensional array with
            //the size we want
            List<Vector3>[,] newArray = new List<Vector3>[cols, rows];
            for (byte initx = 0; initx < cols; initx++)
                for (byte inity = 0; inity < rows; inity++)
                    newArray[initx, inity] = new List<Vector3>();
            //copy the contents of the old array to the new one
            Array.Copy(original, newArray, original.Length);
            //set the original to the new array
            original = newArray;
        }
        private void ResizeRenderer(ref StaticVBRenderer[,][] original, int cols, int rows)
        {
            //create a new 2 dimensional array with
            //the size we want
            StaticVBRenderer[,][] newArray = new StaticVBRenderer[cols, rows][];
            for (byte initx = 0; initx < cols; initx++)
                for (byte inity = 0; inity < rows; inity++)
                    newArray[initx, inity] = new StaticVBRenderer[5];
            //copy the contents of the old array to the new one
            Array.Copy(original, newArray, original.Length);
            //set the original to the new array
            original = newArray;
        }

        private void UpdateLists(int x, int y)
        {
            ResizeArray(ref Grass, x, y);
            ResizeArray(ref Dirt, x, y);
            ResizeArray(ref Water, x, y);
            ResizeArray(ref Stone, x, y);
            ResizeArray(ref Sand, x, y);
            ResizeRenderer(ref staticVBRenderer, x, y);
            //InitResize(x,y);
        }
        Vector2 mapsize;
        private void Expand(Vector2 expansion)
        {
            UpdateLists((int)mapsize.X, (int)expansion.Y); //Prepare / Initialize lists

            //Generate map in chunks
            for (int geny = 0; geny < mapsize.X; geny++)
            {
                for (int x = 0 + (16 * (int)geny); x < width + (16 * (int)expansion.Y - 1); x++)
                {
                    for (int z = 0 + (16 * (int)geny); z < length + (16 * (int)expansion.Y - 1); z++)
                    {
                        //15,10 | 30, 8 | 20, 5 <----- Octave 4/5 combos that are good so far
                        float octave1 = PerlinSimplexNoise.noise((x + seedx) * 0.0001f, 1, (z + seedx) * 0.0001f) * 0;
                        float octave2 = PerlinSimplexNoise.noise((x + seedx) * 0.0005f, 1, (z + seedx) * 0.0005f) * 0f;
                        float octave3 = PerlinSimplexNoise.noise((x + seedx) * 0.005f, 1, (z + seedx) * 0.005f) * 0;
                        float octave4 = PerlinSimplexNoise.noise((x + seedx) * 0.01f, 1, (z + seedx) * 0.01f) * 20f;
                        float octave5 = PerlinSimplexNoise.noise((x + seedx) * 0.03f, 1, (z + seedx) * 0.03f) * 5f;

                        float finalnumber = octave2 + octave3 + octave4 + octave5;

                        float lowerGroundHeight = octave1 + octave2 + octave3 + octave4 + octave5;
                        if ((67 + (int)lowerGroundHeight) <= 64) //Check if water
                        {
                            Sand[(int)geny, (int)expansion.Y - 1].Add(new Vector3(x, 68 + (int)lowerGroundHeight, z)); //Add to water list
                            Water[(int)geny, (int)expansion.Y - 1].Add(new Vector3(x, 65, z)); //Also add to list
                        }
                        else
                            Grass[(int)geny, (int)expansion.Y - 1].Add(new Vector3(x, 67 + (int)lowerGroundHeight, z)); //Add grass to list
                    }
                }
            }
            mapsize = new Vector2(20, expansion.Y);
            for (int geny = 0; geny < mapsize.X; geny++)
            {
                LoadBuffer(geny,(int)expansion.Y - 1); //Load buffers
            }

            //BoundingCreation(); //Create Bounding Boxes.
        }

        private void CubeCreation()
        {
            if (boxmade == false)
            {
                thetestbox.Add(new BoundingBox(new Vector3(2.5f, 68.5f, 2.5f), new Vector3(3.5f, 69.5f, 3.5f))); //create bounding box
                Grass[(int)lastPos.X, (int)lastPos.Y].Add(new Vector3(3, 69, 3)); //Add grass to list
                UnLoadBuffer((int)lastPos.X, (int)lastPos.Y); //relaod buffers
                LoadBuffer((int)lastPos.X, (int)lastPos.Y);
                boxmade = true;
            }
        }

        private void Jump(bool ignore)
        {
            if (jumping == false || ignore == true) //Check if player recently has jumped or if the jumping action is still in progress.
            {
                if (jumping == false)
                {
                    jumping = true; //set player as jumping
                    jumpstart = findtop() + 2; //jump start pos
                }
                if (cameraPosition.Y < jumpstart + 2) //if it's under the player move him down
                {
                    ignorgravity = true; //let player gain altitude
                    //move player upwards
                    cameraPosition.Y += 0.1f;
                    double newcamy = Math.Round(cameraPosition.Y, 2);
                    cameraPosition.Y = (float)newcamy;
                }
                else
                    ignorgravity = false; //pull player back to the floor
            }
        }

        private void UpdateHeight()
        {
            if (ignorgravity == false)
            {
                //Checks to see where the cube is under the player.
                if (cameraPosition.Y < findtop() + 2) //if it's under the player move him down
                {
                    cameraPosition.Y += 0.1f;
                    double newcamy = Math.Round(cameraPosition.Y, 1);
                    cameraPosition.Y = (float)newcamy;
                }
                else if (cameraPosition.Y > findtop() + 2) //if its above, move him up
                {
                    cameraPosition.Y += -0.25f;
                    double newcamy = Math.Round(cameraPosition.Y, 1);
                    cameraPosition.Y = (float)newcamy;

                }
            }
        }

        private void LoadBuffers()
        {
            for (byte chunkx = 0; chunkx < mapsize.X; chunkx++)
            {
                for (byte chunkz = 0; chunkz < mapsize.Y; chunkz++)
                {
                    //Create buffers in chunks for rendering
                    if (Stone[chunkx, chunkz].Count != 0)
                        this.staticVBRenderer[chunkx, chunkz][0] = new StaticVBRenderer(GraphicsDevice, Textures[0], Stone[chunkx, chunkz], staticVBEffect);
                    if (Grass[chunkx, chunkz].Count != 0)
                        this.staticVBRenderer[chunkx, chunkz][1] = new StaticVBRenderer(GraphicsDevice, Textures[1], Grass[chunkx, chunkz], staticVBEffect);
                    if (Dirt[chunkx, chunkz].Count != 0)
                        this.staticVBRenderer[chunkx, chunkz][2] = new StaticVBRenderer(GraphicsDevice, Textures[2], Dirt[chunkx, chunkz], staticVBEffect);
                    if (Water[chunkx, chunkz].Count != 0)
                        this.staticVBRenderer[chunkx, chunkz][3] = new StaticVBRenderer(GraphicsDevice, Textures[3], Water[chunkx, chunkz], staticVBEffect);
                    if (Sand[chunkx, chunkz].Count != 0)
                        this.staticVBRenderer[chunkx, chunkz][4] = new StaticVBRenderer(GraphicsDevice, Textures[4], Sand[chunkx, chunkz], staticVBEffect);
                }
            }
        }

        private void BoundingCreation()
        {
            //Create boudning boxes around chunks for visibility culling. Kinda rough...
            int minx = 0;
            int minz = 0;
            for (int chunkx = 0; chunkx < xychunks; chunkx++)
            {
                for (int chunkz = 0; chunkz < xychunks; chunkz++)
                {
                    //box around the chunks of cubes
                    minx = chunkx * 16;
                    minz = chunkz * 16;
                    thisqube[chunkx, chunkz] = new BoundingBox(new Vector3(minx, 0, minz), new Vector3(minx + 16, 128, minz + 16));
                }
            }
        }

        bool InView(BoundingBox boundingSphere)
        {
            //check to see if the bouding box is visible
            return bf.Intersects(boundingSphere);
        }

        private void AddToCameraPosition(Vector3 vectorToAdd)
        {
            Matrix cameraRotation;
            //update view with position
            if(flying == true) //check if flying
                cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);
            else
                cameraRotation = Matrix.CreateRotationY(leftrightRot);

            Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation);
            cameraPosition += moveSpeed * rotatedVector;
            UpdateViewMatrix();
        }

        private void UpdateViewMatrix()
        {
            //Viewpoint creation
            Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);

            Vector3 cameraOriginalTarget = new Vector3(0, 0, -1);
            Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0);

            Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation);
            Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget;

            Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);

            Ray Cubepick = new Ray(cameraPosition,cameraRotatedTarget);
            //cube selection
            if (lastintersect != 999) //if there was a cube selected before
            {
                if (thetestbox.ElementAt(lastintersect) != null) //make sure the cube still exists
                {
                    if (Cubepick.Intersects(thetestbox.ElementAt(lastintersect)) == null) //check to see if player is still selecting the cube
                    {
                        intersecting = new Vector4(999, 999, 999, 999);
                        lastintersect = 999;
                    }
                }
            }
            else
            {
                for (int i = 0; i < thetestbox.Count; i++) //check the list for intersection
                {
                    if (thetestbox.ElementAt(i) != null)
                    {
                        if (Cubepick.Intersects(thetestbox.ElementAt(i)) != null) //check to see if player is selecting new cube
                        {
                            //update intersection for debug
                            float lastX = thetestbox.ElementAt(i).Min.X + .5f;
                            float lastZ = thetestbox.ElementAt(i).Min.Z + .5f;
                            float lastY = thetestbox.ElementAt(i).Min.Y + .5f;
                            intersecting = new Vector4(lastPos.X, lastPos.Y, lastX, lastZ);
                            lastintersect = i; //update last selected cube
                        }
                        else
                        {
                            intersecting = new Vector4(999, 999, 999, 999);
                            lastintersect = 999;
                        }
                    }
                }
            }
            cubeEffect.View = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector); //create new view camera
        }

        private int findtop()
        {
            if (cameraPosition.X < 0 || cameraPosition.Z < 0)
                return 80;
            if (lastPos != null) //check to see if the block player is standing on is still in the last chunk
            {
                for (int i = 120; i > 0; i--)
                    if (Grass[(int)lastPos.X, (int)lastPos.Y].Contains(new Vector3((int)cameraPosition.X, i, (int)cameraPosition.Z)))
                        return i;
                for (int i = 120; i > 0; i--)
                    if (Water[(int)lastPos.X, (int)lastPos.Y].Contains(new Vector3((int)cameraPosition.X, i, (int)cameraPosition.Z)))
                        return i;
                for (int i = 120; i > 0; i--)
                    if (Sand[(int)lastPos.X, (int)lastPos.Y].Contains(new Vector3((int)cameraPosition.X, i, (int)cameraPosition.Z)))
                        return i;
                for (int i = 120; i > 0; i--)
                    if (Dirt[(int)lastPos.X, (int)lastPos.Y].Contains(new Vector3((int)cameraPosition.X, i, (int)cameraPosition.Z)))
                        return i;
                for (int i = 120; i > 0; i--)
                    if (Stone[(int)lastPos.X, (int)lastPos.Y].Contains(new Vector3((int)cameraPosition.X, i, (int)cameraPosition.Z)))
                        return i;
            }
            //If not, check other chunks.
            for (int chunkx = 0; chunkx < xychunks; chunkx++)
                for (int chunkz = 0; chunkz < xychunks; chunkz++)
                    for (int i = 120; i > 0; i--)
                    {
                        if (Grass[chunkx, chunkz].Contains(new Vector3((int)cameraPosition.X, i, (int)cameraPosition.Z)))
                        {
                            lastPos = new Vector2(chunkx, chunkz);
                            return i;
                        }
                        if (Water[chunkx, chunkz].Contains(new Vector3((int)cameraPosition.X, i, (int)cameraPosition.Z)))
                        {
                            lastPos = new Vector2(chunkx, chunkz);
                            return i;
                        }
                        if (Sand[chunkx, chunkz].Contains(new Vector3((int)cameraPosition.X, i, (int)cameraPosition.Z)))
                        {
                            lastPos = new Vector2(chunkx, chunkz);
                            return i;
                        }
                        if (Dirt[chunkx, chunkz].Contains(new Vector3((int)cameraPosition.X, i, (int)cameraPosition.Z)))
                        {
                            lastPos = new Vector2(chunkx, chunkz);
                            return i;
                        }
                        if (Stone[chunkx, chunkz].Contains(new Vector3((int)cameraPosition.X, i, (int)cameraPosition.Z)))
                        {
                            lastPos = new Vector2(chunkx, chunkz);
                            return i;
                        }

                    }
            return 80;
        }
    }
}
